/// Copyright 2012, Alex Averian, Yonan Research
/// Released under BSD Licence

#include "stdafx.h"
#include "qzipfile.h"




//#include "assert.h"

#define MINIZ_NO_STDIO
#define MINIZ_NO_ARCHIVE_APIS
#define MINIZ_NO_TIME
#define MINIZ_NO_ZLIB_APIS
#define MINIZ_NO_MALLOC
#define MINIZ_NO_ZLIB_COMPATIBLE_NAMES
#include "qminiz.c"


using namespace micro::qzip;



#define FTEXT		1
#define FHCRC		2
#define FEXTRA		4
#define FNAME		8
#define FCOMMENT	16

// gzip is based on the DEFLATE algorithm, which is a combination of LZ77 and Huffman coding.
// DEFLATE was intended as a replacement for LZW and other patent-encumbered data compression algorithms which,
// at the time, limited the usability of compress and other popular archivers.


//FORMAT
// a 10-byte header, containing a magic number, a version number and a timestamp
// optional extra headers, such as the original file name,
// a body, containing a DEFLATE-compressed payload
// an 8-byte footer, containing a CRC-32 checksum and the length of the original uncompressed data
//


// A compliant compressor must produce files with correct ID1, ID2, CM, CRC32, and ISIZE,
// but may set all the other fields in the fixed-length part of the header to default values (255 for OS, 0 for all others).
// The compressor must set all reserved bits to zero.




#define my_max(a,b) (((a) > (b)) ? (a) : (b))
#define my_min(a,b) (((a) < (b)) ? (a) : (b))

// IN_BUF_SIZE is the size of the file read buffer.
// IN_BUF_SIZE must be >= 1
#define IN_BUF_SIZE (1024*512*2)
static uint8 s_inbuf[IN_BUF_SIZE];

// COMP_OUT_BUF_SIZE is the size of the output buffer used during compression.
// COMP_OUT_BUF_SIZE must be >= 1 and <= OUT_BUF_SIZE

#define COMP_OUT_BUF_SIZE (1024*512*2)

// OUT_BUF_SIZE is the size of the output buffer used during decompression.
// OUT_BUF_SIZE must be a power of 2 >= TINFL_LZ_DICT_SIZE (because the low-level decompressor not only writes, but reads from the output buffer as it decompresses)
//#define OUT_BUF_SIZE (TINFL_LZ_DICT_SIZE)

#define OUT_BUF_SIZE (1024*512*2)
static uint8 s_outbuf[OUT_BUF_SIZE];

// tdefl_compressor contains all the state needed by the low-level compressor so it's a pretty big struct (~300k).
// This example makes it a global vs. putting it on the stack, of course in real-world usage you'll probably malloc() or new it.



QGzipFile::QGzipFile( void )
{
    m_fSize = 0;//archive

    m_time = 0;		//inner file
    m_crc16 = 0;	//  header crc

    m_crc32 = 0;	//crc32 of uncompresed data
    m_iSize = 0;	//uncompresed file size

    make_crc_table();
}


QGzipFile::~QGzipFile( void )
{
}




// void storLE32 ( uint8 * p, uint32 n )
// {
//     p[0] = n >> 0;
//     p[1] = n >> 8;
//     p[2] = n >> 16;
//     p[3] = n >> 24;
// }

bool QGzipFile::Extract( LPCTSTR path, LPCTSTR  target, void * progress )
{
#pragma pack(push, 1)
    struct GZHeader
    {
        uint8	id1;
        uint8	id2;
        uint8	cm;
        uint8	flags;
        uint32	time;
        uint8	xflags;
        uint8	os;
    };
#pragma pack(pop)

    try
    {
        m_filePath = path;

        m_map.Open( m_filePath );

        if ( !m_map.IsOpen() )
        {
            throw L"file open error";
        }

        m_fSize = m_map.GetMemSize();
        QMemReader buffer( m_map.GetMemory(), m_fSize );


        ULONGLONG offset = 0;
        GZHeader gzh  = buffer.Get<GZHeader>( offset );
        offset += sizeof( GZHeader );

        if ( gzh.id1 != 31 || gzh.id2 != 139 )
            throw L"invalid archive";

        if ( gzh.cm != 8 )
            throw L"only deflate supported";

        m_time = gzh.time;
        //cout << setw( 10 ) << "gmtime: " << asctime( gmtime( &tim ) );// << endl;
        //cout << setw( 10 ) << "loctime: " << asctime( localtime( &tim ) );// << endl;


        //xflags compression mode, 2 and 4
        if ( gzh.xflags == 2 )
        {
            //compressor used maximum compression
            //m_mode = 2;
        }
        else if ( gzh.xflags == 4 )
        {
            //compressor used fastest algorithm
            //m_mode = 4;
        }
        else
        {
            //throw L"compression mode unknown";
        }

        //biti 5-7 trebuie sa fie zero
        if ( 224 & gzh.flags ) // 224 = 128 + 64 + 32
        {
            throw "unknown extra fields";
        }

        if ( FTEXT & gzh.flags )
        {
            //nu afecteaza datele
        }

        if ( FEXTRA & gzh.flags )
        {
            //cout << setw( 20 ) << "extra is prezent" << endl;
            //skip extra
            uint16 xlen =  buffer.Get<uint16>( offset );
            offset += sizeof( uint16 );
            offset += xlen;

//             uint8 ch = 0;
//             while ( ch = buffer.Get<uint8>( offset ) )
//             {
// 				offset++;
//             }

        }

        if ( FNAME  & gzh.flags )
        {
            // If FNAME is set, an original file name is present, terminated by a zero byte.
            // The name must consist of ISO 8859-1 (LATIN-1) characters;
            uint8 ch = 0;

            do
            {
                ch = buffer.Get<uint8>( offset );
                offset++;

                if ( ch )
                    m_internalName += ch;  //nu merge adaugarea lui nul
            }
            while ( ch );

        }


        if ( FCOMMENT   & gzh.flags )
        {
            uint8 ch = 0;

            do
            {
                ch = buffer.Get<uint8>( offset );
                offset++;

                if ( ch )
                    m_comment += ch;  //nu merge adaugarea lui nul
            }
            while ( ch );


        }

        if ( FHCRC & gzh.flags )
        {
            // If FHCRC is set, a CRC16 for the gzip header is present, immediately before the compressed data.
            // The CRC16 consists of the two least significant bytes of the CRC32 for all bytes of the gzip header
            // up to and not including the CRC16. [The FHCRC bit was never set by versions of gzip up to 1.2.4,
            // even though it was documented with a different meaning in gzip 1.2.4.]
            //cout << setw( 20 ) << "crc is prezent" << endl;


            m_crc16 = buffer.Get<uint16>( offset );
            offset += sizeof( uint16 );
        }

        //offset
        LPBYTE data = m_map.GetMemory() + offset;
        size_t size = m_fSize - offset - 8;

        //crc32 of initial data
        m_crc32 = buffer.Get<uint32>( m_fSize - 8 );

        //size of initial data
        m_iSize = buffer.Get<uint32>( m_fSize - 4 );


        //if(m_internalName.IsEmpty())
        //	m_internalName = target;
        QFile output;
        output.Open( target, QFile::modeCreate | QFile::typeBinary );

        if ( !output )
        {
            throw L"open target file error";
        }

        //////////////////////////////////////////////////////////////////////////
        // Decompression.


        void * next_out = s_outbuf;
        size_t avail_out = OUT_BUF_SIZE;
        size_t total_in = 0;
        size_t total_out = 0;

        const void * next_in = data;
        size_t avail_in = size;
        size_t infile_remaining = size;

        tinfl_decompressor inflator;
        tinfl_init( &inflator );

        while ( true )
        {
            size_t in_bytes = avail_in;
            size_t out_bytes = avail_out;

            tinfl_status status = tinfl_decompress( &inflator, ( const mz_uint8 * )next_in, &in_bytes,
                                                    s_outbuf,  ( mz_uint8 * )next_out, &out_bytes,
                                                    ( infile_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0 )       );//| TINFL_FLAG_PARSE_ZLIB_HEADER );

            ///
            infile_remaining -= in_bytes;

            avail_in -= in_bytes;
            next_in = ( const mz_uint8 * )next_in + in_bytes;
            total_in += in_bytes;

            avail_out -= out_bytes;
            next_out = ( mz_uint8 * )next_out + out_bytes;
            total_out += out_bytes;

            if ( ( status <= TINFL_STATUS_DONE ) || ( 0 == avail_out ) )
            {
                // Output buffer is full, or decompression is done, so write buffer to output file.
                size_t n = OUT_BUF_SIZE -  avail_out;

                if ( output.Write( s_outbuf, n ) != n )
                {
                    //output.Close();
                    throw  L"error writing to output file";
                }

                next_out = s_outbuf;
                avail_out = OUT_BUF_SIZE;
            }

            // If status is <= TINFL_STATUS_DONE then either decompression is done or something went wrong.
            if ( status <= TINFL_STATUS_DONE )
            {
                if ( status == TINFL_STATUS_DONE )
                {
                    // Decompression completed successfully.
                    break;
                }
                else
                {
                    // Decompression failed.
                    throw  L"decompressing error";// status );
                }
            }
        }//for


    }//try
    catch ( LPCTSTR  err )
    {
        ::DeleteFile( target );
        throw err;
        return false;
    }

    return true;
}

bool QGzipFile::Compress( LPCTSTR path, LPCTSTR  target, void * progress )
{
    uint8 hdr[10] =
    {
        31, 139,	/* magic */
        8,			/* z method */
        0,			/* flags */
        0, 0, 0, 0,	/* mtime */
        0,			/* xfl */
        0xFF,		/* OS */
    };

    try
    {
        m_filePath = path;

        m_map.Open( m_filePath );

        if ( !m_map.IsOpen() )
        {
            throw L"file open error";
        }

        LPBYTE data = m_map.GetMemory();
        m_fSize = m_map.GetMemSize();


        //size of initial data
        m_iSize = m_fSize;

        //crc32 of initial data
        m_crc32 =ComputeCRC( data, m_fSize );



        //if(m_internalName.IsEmpty())
        //	m_internalName = target;
        QFile output;
        output.Open( target, QFile::modeCreate | QFile::modeWrite | QFile::typeBinary );

        if ( !output )
        {
            throw L"error create target file";
        }

        if ( output.Write( hdr, sizeof( hdr ) ) != sizeof( hdr ) )
        {
            throw   L"error writing to output";
        }

        //headers


        //////////////////////////////////////////////////////////////////////////
        // Compression

        // The number of dictionary probes to use at each compression level (0-10). 0=implies fastest/minimal possible probing.
        static const mz_uint probes[11] = { 0, 1, 6, 32,  16, 32, 128, 256,  512, 768, 1500 };


        int level = 9;
        // create tdefl() compatible flags (we have to compose the low-level flags ourselves, or use tdefl_create_comp_flags_from_zip_params() but that means MINIZ_NO_ZLIB_APIS can't be defined).
        mz_uint comp_flags = TDEFL_COMPUTE_ADLER32 | probes[ MZ_MIN( 9, level )];




        tdefl_compressor g_deflator;

        // Initialize the low-level compressor.
        tdefl_status status = tdefl_init( &g_deflator, nullptr, nullptr, comp_flags );

        if ( status != TDEFL_STATUS_OKAY )
        {
            throw L"compress error";
        }

        void * next_out = s_outbuf;
        size_t avail_out = OUT_BUF_SIZE;
        size_t total_in = 0;
        size_t total_out = 0;

        const void * next_in = data;
        size_t avail_in = m_fSize;
        size_t infile_remaining = m_fSize;

        // Compression.
        while ( true )
        {
            size_t in_bytes = avail_in;
            size_t out_bytes = avail_out;

            // Compress as much of the input as possible (or all of it) to the output buffer.
            status = tdefl_compress( &g_deflator, next_in, &in_bytes,
                                     next_out, &out_bytes, infile_remaining ? TDEFL_NO_FLUSH : TDEFL_FINISH );
            ///
            infile_remaining -= in_bytes;


            next_in = ( const char * )next_in + in_bytes;
            avail_in -= in_bytes;
            total_in += in_bytes;

            next_out = ( char * )next_out + out_bytes;
            avail_out -= out_bytes;
            total_out += out_bytes;

            if ( ( status != TDEFL_STATUS_OKAY ) || ( 0 == avail_out ) )
            {
                // Output buffer is full, or compression is done or failed, so write buffer to output file.
                size_t n = OUT_BUF_SIZE -  avail_out;

                if ( output.Write( s_outbuf, n ) != n )
                {
                    throw   L"error writing to output file";
                }

                next_out = s_outbuf;
                avail_out = OUT_BUF_SIZE;
            }

            if ( status == TDEFL_STATUS_DONE )
            {
                // Compression completed successfully.
                break;
            }
            else if ( status != TDEFL_STATUS_OKAY )
            {
                // Compression somehow failed.
                throw L"compress error";
            }
        }

        //crc32 and size
        output.Write( &m_crc32, sizeof( m_crc32 ) );
        output.Write( &m_iSize, sizeof( m_iSize ) );

    }//try
    catch ( LPCTSTR err )
    {
        ::MessageBox( nullptr, err, L"Error", MB_OK );
        //::DeleteFile( target );
        throw err;
        return false;
    }

    return true;
}


// The following sample code represents a practical implementation of the CRC (Cyclic Redundancy Check).
// (See also ISO 3309 and ITU-T V.42 for a formal specification.)

/* Make the table for a fast CRC. */
void QGzipFile::make_crc_table( void )
{
    uint32 c;

    for ( int  n = 0; n < 256; n++ )
    {
        c = ( unsigned long ) n;

        for ( int  k = 0; k < 8; k++ )
        {
            if ( c & 1 )
            {
                c = 0xedb88320L ^ ( c >> 1 );
            }
            else
            {
                c = c >> 1;
            }
        }

        crc_table[n] = c;
    }
}

/*
 Update a running crc with the bytes buf[0..len-1] and return
 the updated crc. The crc should be initialized to zero. Pre- and
 post-conditioning (one's complement) is performed within this
 function so it shouldn't be done by the caller. Usage example:

   unsigned long crc = 0L;

   while (read_buffer(buffer, length) != EOF)
   {
     crc = update_crc(crc, buffer, length);
   }
   if (crc != original_crc) error();
*/

uint32 QGzipFile::update_crc( uint32 crc,  unsigned char * buf, int len )
{

    // assert( crc_table_computed );
    unsigned long c = crc ^ 0xffffffffL;

    for ( int n = 0; n < len; n++ )
    {
        c = crc_table[( c ^ buf[n] ) & 0xff] ^ ( c >> 8 );
    }

    return c ^ 0xffffffffL;
}

/* Return the CRC of the bytes buf[0..len-1]. */
uint32 QGzipFile::ComputeCRC( unsigned char * buf, int len )
{
    return update_crc( 0L, buf, len );
}


//////////////////////////////////////////////////////////////////////////

QZipFile::QZipFile( void )
{
}


QZipFile::~QZipFile( void )
{
}

